﻿using System.Reflection.Emit;
using System.Reflection;
using System;
using ProxyViewModel.Attributes;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;
namespace ProxyViewModel.Emitors {
	internal class RaisePropertyChangedMethodEmitor : EmitorBase {
		public RaisePropertyChangedMethodEmitor(TypeBuilder typeBuilder, Type operatable, IDictionary<string, object> parameters)
			: base(typeBuilder, operatable, parameters) { }

		protected override void EmitContent() {
			MethodInfo methodRaisePropertyChanged = null;

			methodRaisePropertyChanged = SearchMethodByAttribute();
			if (methodRaisePropertyChanged == null) {
				methodRaisePropertyChanged = SearchMethodByName();
			}

			if (methodRaisePropertyChanged == null) {
				methodRaisePropertyChanged = GenerateRaisePropertyChanged();
			}

			CurrentParameters[Parameters.RaisePropertyChangedMethodKey] = methodRaisePropertyChanged;
		}

		private MethodInfo GenerateRaisePropertyChanged() {
			MethodBuilder methodBuilder = TypeBuilder.DefineMethod("RaisePropertyChanged", MethodAttributes.Public, null,
				new Type[] { typeof(string) });

			ILGenerator ilGenerator = methodBuilder.GetILGenerator();
			if (GetParameter<FieldInfo>(Parameters.PropertyChangedFieldKey) != null) {
				GenerateEventInvokeNormal(ilGenerator, GetParameter<FieldInfo>(Parameters.PropertyChangedFieldKey));
			}
			else {
				GenerateEventInvokeForPrivateField(ilGenerator);
			}
			return methodBuilder;
		}

		private void GenerateEventInvokeNormal(ILGenerator il, FieldInfo propertyChangedField) {
			il.DeclareLocal(typeof(bool));
			il.Emit(OpCodes.Nop);
			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Ldfld, propertyChangedField);
			il.Emit(OpCodes.Ldnull);
			il.Emit(OpCodes.Ceq);
			il.Emit(OpCodes.Stloc_0);
			il.Emit(OpCodes.Ldloc_0);
			Label lbl1 = il.DefineLabel();
			il.Emit(OpCodes.Brtrue_S, lbl1);
			il.Emit(OpCodes.Nop);
			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Ldfld, propertyChangedField);
			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Ldarg_1);
			il.Emit(OpCodes.Newobj, typeof(PropertyChangedEventArgs).GetConstructor(new Type[] { typeof(string) }));
			il.Emit(OpCodes.Callvirt, typeof(PropertyChangedEventHandler).GetMethod("Invoke", new Type[] { typeof(object), typeof(PropertyChangedEventArgs) }));
			il.Emit(OpCodes.Nop);
			il.MarkLabel(lbl1);
			il.Emit(OpCodes.Nop);
			il.Emit(OpCodes.Ret);
		}

		private void GenerateEventInvokeForPrivateField(ILGenerator il) {
			il.Emit(OpCodes.Ldarg_1);
			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Call, typeof(ReflectionHelper).GetMethod("RaisePropertyChangedEvent"));
			il.Emit(OpCodes.Ret);
		}

		private MethodInfo SearchMethodByName() {
			return ReflectionHelper.GetMethod(Operatable, "RaisePropertyChanged", typeof(string));
		}

		private MethodInfo SearchMethodByAttribute() {

			MethodInfo info = null;

			// search using attribute:
			IList<MethodInfo> markedMethods = ReflectionHelper.GetMethodsMarkedWithAttribute<RaisePropertyChangedMethodAttribute>(Operatable);

			if (markedMethods.Count > 1) {
				throw new Exception("Only one method in the ViewModel can be marked with RaisePropertyChangedMethodAttribute");
			}

			if (markedMethods.Count == 1) {
				// checking parameters. it must be only a parameter and type of string

				info = markedMethods.First();

				if (info.GetParameters().Count() != 1) {
					throw new Exception("Invalid parameters for method marked with RaisePropertyChangedMethodAttribute. It is required that method must have only one string parameter");
				}

				if (info.GetParameters()[0].ParameterType != typeof(string)) {
					throw new Exception("Invalid parameters for method marked with RaisePropertyChangedMethodAttribute. It is required that method must have only one string parameter");
				}
			}

			return info;
		}
	}
}
